const FUN = require('../comm/func');
const rCache = require('../model/redis_cache');
const timLib = require('../comm/time-lib');
const PMR=require('../utils/policyManger');
const DB = require('../model/model_data');
const dataCalc = require('../comm/data-calc');

const DAY_SEC = 86400;

// 量化 - 日数据积累环比
module.exports = class Futures_Hedge
{
  // 将数据库选项缓存到redis
  // policy 来自数据库的记录，附加了user_auth,user_level字段
  // opts 数据库json字段的解析对象
  // CacheCallback 存储执行函数
  static InitCache(policy, opts, CacheCallback)
  {
    let dConf = PMR.baseParam(policy); // 即将缓存到redis的参数

    // 用模板参数替换配置项内容
    if (opts.args && opts.args != 'undefined') {
      let argsArr = opts.args.split(',');
      for (let i in argsArr) { // 将#1,#2...替换
        let argStr = new RegExp(`#${parseInt(i)+1}`,'g'); // 用正则的方式可以替换所有匹配
        let realArg = argsArr[i].split('@')[0]; // 去掉注释部分
        opts.topics = opts.topics.replace(argStr, realArg);
        opts.calc = opts.calc.replace(argStr, realArg);
      }
    }
    pcyLog(`[${dConf.policy}][${dConf.policy_id}][${dConf.policy_name}] topics`, opts.topics);
    //pcyLog(`[${dConf.policy}][${dConf.policy_id}] calc`, opts.calc);

    // 缓存参数
    dConf.topics = opts.topics; // 主题列表，分号分隔
    dConf.calc = opts.calc; // 算式列表，|分隔
    dConf.time_scope = opts.time_scope; // 时间段字符串
    dConf.period = opts.period; // 统计周期
    dConf.record_limit = policy.record_limit; // 数据库条数上限
    dConf.record_duration = 0; // 数据库周期上限
    dConf.tidy_period = 100;
    dConf.base = opts.base; // 补充参数：计算增量的起始基点
    dConf.first_day = opts.first_day; // 补充参数：起始日

    if (timLib.NowTimeInScope(opts.time_scope, '213000')) { // 判断是不是夜盘品种.
      pcyLog(`[${dConf.policy}][${dConf.policy_id}][${dConf.policy_name}] has night trade.`);
      dConf.night_class = true;
    } else {
      pcyLog(`[${dConf.policy}][${dConf.policy_id}][${dConf.policy_name}] not night.`);
      dConf.night_class = false;
    }

    // 订阅用户主题 
    let funcList = [];
    let topicList = opts.topics.split(',');
    for (let i = 0; i < topicList.length; i++) {
      let tArr = topicList[i].split('/');
      funcList.push(tArr[1]);
      PMR.subMqttTopic(topicList[i], policy.type, policy.id);
    }
    PMR.subMqttTopic('forward/sync', policy.type, policy.id); // 同步信号

    dConf.funcs = funcList.join(',');
    CacheCallback(policy.id, policy.master, dConf); // 记录到缓存
    rCache.setKey(PMR.rdNode(dConf), 'count', 0); // 计数器
  } // end InitCache

  // 策略被删除
  // policy 来自数据库的记录
  static Exit(policy, opts)
  {
    pcyLog(`[${policy.type}][${policy.id}][${policy.name}] exit topics`, opts.topics);
    //pcyLog(`[${dConf.policy}][${dConf.policy_id}] exit calc`, opts.calc);

    // 取消主题订阅，必须与init时对应，否则会产生冗余订阅
    let topicList = opts.topics.split(',');
    for (let i = 0; i < topicList.length; i++) {
      PMR.cancleMqttTopic(topicList[i], policy.type, policy.id);
    }
    PMR.cancleMqttTopic('forward/sync', policy.type, policy.id); // 同步信号
  }

  // 获取一个节点下所有子节点并将其保存成obj数组输出
  static async GetNodeToObj(path, filter=null)
  {
    let nodes = await rCache.keys(path, '*');
    if (!nodes) return {};

    let retObj = {};
    for(let i = 0; i < nodes.length; i++) { // 轮询每个子节点
      let p = nodes[i].split(':');
      let name = p[p.length-1]; // 提取节点名
      if (filter && !filter.includes(name)) {
        continue;
      }
      retObj[name] = await rCache.getAll(nodes[i]);
    }
    return retObj;
  }

  // 处理每个变量算式的计算
  static VarCalcHander(calcStr, prices)
  {
    let vars = calcStr.match(/[$][A-Za-z0-9.]+[$]/g);
    for (let i = 0; i < vars.length; i++) {
      let varName = vars[i].replace(/[$]/g, ''); // 去掉首尾$S
      varName = varName.split('.'); // 分隔尾缀
      if (!Object.keys(prices).includes(varName[0])) return 0; // prices中缺少对象值，无法处理
      if (varName.length == 1) {
        if (!isVaild(prices[varName[0]].NOW)) return 0;
        let matchReg = new RegExp(`[$]${varName[0]}[$]`, 'g');
        calcStr = calcStr.replace(matchReg , prices[varName[0]].NOW);
      } else if (varName.length == 2) {
        let matchReg = new RegExp(`[$]${varName[0]}[.]${varName[1]}[$]`, 'g');
        if (!isVaild(prices[varName[0]][varName[1]])) return 0;
        calcStr = calcStr.replace(matchReg , prices[varName[0]][varName[1]]);
      }
    }

    let calcVal = Number(eval(calcStr)).toFixed(2); // 通过算式计算数值
    return calcVal;
  }

  // 读取本时段base值，读不到返回null
  static async ReadBaseValue(dConf, day, addr)
  {
    let node = null;
    let hourMin = parseInt(timLib.CurrHourMinute());
    if (hourMin >= 800 && hourMin < 1200) { // 早盘
      node = `base:a.${day}.${addr}`;
    } else if (hourMin >= 1200 && hourMin < 2000) { // 午盘
      if (dConf.base == 'open-start') node = `base:b.${day}.${addr}`;
      else if (dConf.base == 'day-night-start') node = `base:a.${day}.${addr}`;
    } else if (hourMin >= 2000 && hourMin < 2400) { // 晚盘
      node = `base:c.${day}.${addr}`;
    } else if (hourMin >= 0 && hourMin < 800) { // 夜盘，有日期跨天漏洞，todo
      node = `base:d.${day}.${addr}`;
    }
    if (node) {
      return await rCache.getKey(PMR.rdNode(dConf), node);
    }
    return null;
  }

  // 写入本时段base值
  static SetBaseValue(dConf, day, addr, baseVal)
  {
    let node = null;
    let hourMin = parseInt(timLib.CurrHourMinute());
    if (hourMin >= 800 && hourMin < 1200) { // 早盘
      node = `base:a.${day}.${addr}`;
    } else if (hourMin >= 1200 && hourMin < 2000) { // 午盘
      node = `base:b.${day}.${addr}`;
    } else if (hourMin >= 2000 && hourMin < 2400) { // 晚盘
      node = `base:c.${day}.${addr}`;
    } else if (hourMin >= 0 && hourMin < 800) { // 夜盘，有日期跨天漏洞，todo
      node = `base:d.${day}.${addr}`;
    }
    if (node) {
      rCache.setKey(PMR.rdNode(dConf), node, baseVal, DAY_SEC);
    }
  }

  // 拿到今天之前最后一天的数据后进行处理并存储
  static LastRcdDateHandle(dConf, stamp, lastRcd, valObj, saveObj, diffObj, first_day, del_days, valBase)
  {
    // 获取到数据后处理
    if (lastRcd) { // 处理有正常数据后
      let lastValObj = JSON.parse(lastRcd.json); // 上一个交易日的记录，记录了前面所有天的数据
      for (let key in lastValObj) { // 遍历历史值
        let keyArr = key.split('.'); // key = 20240403.1
        if (keyArr.length < 2) continue;
        let date = keyArr[0];
        let addr = keyArr[1];
        let dayNum = INT(date);

        if (isNaN(dayNum) || isNaN(first_day)) continue;
        if (dayNum < first_day) continue; // 起始日之前的数据不处理
        if (del_days.includes(dayNum)) continue; // 删除名单中的日期不处理
        saveObj[key] = lastValObj[key]; // 将历史值附加到今天的存储对象中
        //pcyLog('[days-ratio]', dConf.policy_id, key, saveCnt++);

        // 计算与今天的差值，分地址存储到diffObj中
        if (isVaild(valObj[addr])) {
          if (!isVaild(diffObj[addr]))
            diffObj[addr] = {};
          diffObj[addr][date] = N(valObj[addr] - valBase[addr] - lastValObj[key]).toFixed(2); // 与今天的差值
        }
      }
    } else {
      pcyLog(`[${dConf.policy}][${dConf.policy_id}][${dConf.policy_name}] last day record is null.`);
    }

    // 存数据到db
    rCache.setKey(PMR.rdNode(dConf), 'db_stamp', stamp, 60); // 记录时间戳
    // pcyLog('SavePrevObj2DB cost ', timLib.NowStamp()-stamp);

    if (diffObj.length < 15) { // 如果记录数到达15，则不再记录新的
      let tmpArr = [dConf.hwid, dConf.policy_id, 0, JSON.stringify(saveObj), timLib.Rounding(dConf.period, stamp)];
      DB.newRcd(dConf.uid, 'dat_json', 'hwid,policy_id,addr,json,stamp', tmpArr).then((insertId)=>{
        rCache.setKey(PMR.rdNode(dConf), 'need_inform', 1);
      }) // end then
    }

    // 存储差值
    for (let addr in diffObj) {
      let tmpArr2 = [dConf.hwid, dConf.policy_id, addr, JSON.stringify(diffObj[addr]), timLib.Rounding(dConf.period, stamp)];
      DB.newRcd(dConf.uid, 'dat_json', 'hwid,policy_id,addr,json,stamp', tmpArr2).then((insertId)=>{
        rCache.setKey(PMR.rdNode(dConf), 'need_inform', 1);
      }) // end then
    }

  } // end LastRcdDateHandle

  // 存储前一段数据到数据库
  static async SavePrevObj2DB(dConf, stamp)
  {
    let prevNode = PMR.rdNode(dConf)+':'+timLib.PrevRounding(dConf.period, stamp);
    let valObj = await rCache.getAll(prevNode, null);
    if (valObj == null) {
      return pcyLog(`[${dConf.policy}][${dConf.policy_id}][${dConf.policy_name}] prev cache data is null!`);
    }

    let count = await rCache.getKey(PMR.rdNode(dConf), 'count');
    let today = timLib.CurrYearMonthDay();
    //let today = timLib.StampToTimeNum(stamp, 'yyyyMMdd');

    // 第一天是起始天，后面所有带~的为屏蔽天
    let day_filter = dConf.first_day.split(',');
    let first_day = INT(day_filter[0]);
    let del_days = [];
    for (let day of day_filter) { // 将起始日后，带~的日期加入删除日期数组
      let num = INT(day.substring(1));
      if (day.charAt(0) == '~' && num) del_days.push(num);
    }

    let saveObj = {}; // 要存储的源数据
    let diffObj = []; // 要存储的历史源数据与今天的差值，一个地址一个对象

    // 处理有基础起点值的分支
    let valBase = []; // 基础值，常规模式下为0
    for (let addr in valObj) { // addr = 1,2,3...
      valBase[addr] = 0;
      if (dConf.base == 'open-start' || dConf.base == 'day-night-start') { // 按段开盘价为起点计算
        valBase[addr] = await this.ReadBaseValue(dConf, today, addr);
        if (valBase[addr] == null) { // 第一次存
          valBase[addr] = valObj[addr];
          this.SetBaseValue(dConf, today, addr, valObj[addr]);
        } else if (N(valBase[addr]) > N(valObj[addr])) { // 垃圾数据干扰
          pcyLog(`[${dConf.policy_id}][addr:${addr}] correct base(${valBase[addr]}) to ${valObj[addr]}`);
          valBase[addr] = valObj[addr];
          this.SetBaseValue(dConf, today, addr, valObj[addr]);
        }
      }

      // 今天的新数据加时间标签存入
      saveObj[`${today}.${addr}`] = N(valObj[addr] - valBase[addr]).toFixed(2); // [20240415.1] = xxx
    }

    // 先读取上一个交易日的数据
    DB.findLastOneByStampScape(dConf.uid, dConf.table, dConf.policy_id, stamp, dConf.period, [0]).then((lastRcd) => {
      this.LastRcdDateHandle(dConf, stamp, lastRcd, valObj, saveObj, diffObj, first_day, del_days, valBase); // 然后进行异步处理
      // 删除历史冗余
      dataCalc.dbRecordTidy(dConf.uid, dConf.table, dConf.policy_id, dConf.record_limit);
      // 计数自增
      rCache.incKey(PMR.rdNode(dConf), 'count');
    }) // end DB.findLastOneByStampScape
  } // end SavePrevObj2DB

  // 处理数据，存储到redis或db
  static async ValObjHandler(dConf, priceObjs, stamp)
  {
    let calcList = dConf.calc.split(',');
    let nodeStamp = PMR.rdNode(dConf)+':'+timLib.Rounding(dConf.period, stamp);
    let currObj = await rCache.getAll(nodeStamp, null); // 获取本时间段的记录
    if (currObj == null) { // 第一次存储
      if (dConf.night_class == 'true') { // 带夜盘的品种

      }
      this.SavePrevObj2DB(dConf, stamp); // 存储旧的到数据库

      let newObj = {};
      for (let i = 0; i < calcList.length; i++) { // 处理每个算式
        let value = this.VarCalcHander(calcList[i], priceObjs);
        newObj[`${i+1}`] = value;
      }
      rCache.set(nodeStamp, null, newObj, dConf.period*2); // 新建新的到缓存
      // pcyLog('first', newObj);
    } else { // 非第一次存储
      for (let i = 0; i < calcList.length; i++) { // 处理每个算式
        let value = this.VarCalcHander(calcList[i], priceObjs);
        currObj[`${i+1}`] = value;
      }
      rCache.set(nodeStamp, null, currObj, dConf.period*2);
      // pcyLog('continue', currObj);
    }
  }

  // 处理数据
  // topic = {project,devType,func,devId}
  // dConf即InitCache中的obj
  // dataBuf即mq消息内容，buf对象
  static async DataIn(topic, dConf, DataBuf)
  {
    if (!timLib.NowTimeInScope(dConf.time_scope)) return; // 超过监控时间段了

    let stamp = timLib.NowStamp();
    let funcList = dConf.funcs.split(','); // 品种列表

    if (topic.func == 'sync') { // 同步信号
      let priceObjs = await this.GetNodeToObj(PMR.rdNode(dConf), funcList); // 获取到所有价格信息
      this.ValObjHandler(dConf, priceObjs, stamp); // 处理数据

      // 延时1秒后看db是否变动，通知前台
      setTimeout(()=>{
        rCache.getKey(PMR.rdNode(dConf), 'need_inform').then((needInform)=>{
          if (needInform != null) PMR.policyInform('update', dConf); // 通知前端
          rCache.del(PMR.rdNode(dConf), 'need_inform');
        })
      }, 1000);
    } else if (funcList.includes(topic.func)) { // 价格通知
      let priceObj = JSON.parse(DataBuf.toString()); // 获得价格新值
      rCache.set(PMR.rdNode(dConf), topic.func, priceObj, DAY_SEC); // 缓存价格信息
    }

    return;
  }

}